package com.yfunc.common.event.store;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Random;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.yfunc.common.event.Event;
import com.yfunc.common.event.EventException;
import com.yfunc.common.event.EventStore;
import com.yfunc.common.event.annotations.Storage;
import com.yfunc.common.persistence.Session;

public class DefaultEventStore implements EventStore {

	private Session query;

	public DefaultEventStore(Session query) {
		this.query = query;
	}

	@Override
	public void append(Event event) {
		String data = toJsonString(event);
		EventLog eventLog = new EventLog(data, event.getClass().getName(),
				System.currentTimeMillis(), version());
		this.query.insert(eventLog);
	}

	@Override
	public <T extends Event> T findLatestEvent(final Class<T> eventType) {
		EventLog eventLog = this.query.selectOne(
				"EventStore.findLatestEvent", eventType.getName());
		if (eventLog == null) {
			return null;
		}
		return (T) toEventObject(eventLog.getType(), eventLog.getData());
	}

	@Override
	public <T extends Event> T findEvent(Class<T> eventType, long version) {
		HashMap<String, Object> params = new HashMap<String, Object>();
		params.put("type", eventType.getName());
		params.put("version", version);
		EventLog eventLog = this.query.selectOne("EventStore.findEvent",
				params);
		if (eventLog == null) {
			return null;
		}
		return (T) toEventObject(eventLog.getType(), eventLog.getData());
	}

	private Event toEventObject(String type, String data) {
		try {
			Event event = (Event) Class.forName(type).newInstance();
			JSONObject json = (JSONObject) JSON.parse(data);
			for (Field f : event.getClass().getDeclaredFields()) {
				if (f.isAnnotationPresent(Storage.class)) {
					Object value = json.getObject(f.getName(), f.getType());
					if (value != null) {
						f.setAccessible(true);
						f.set(event, value);
					}
				}
			}
			return event;
		} catch (Exception e) {
			throw new EventException(e);
		}
	}

	private String toJsonString(Event event) {
		try {
			JSONObject json = new JSONObject();
			for (Field f : event.getClass().getDeclaredFields()) {
				if (f.isAnnotationPresent(Storage.class)) {
					f.setAccessible(true);
					json.put(f.getName(), f.get(event));
				}
			}
			return json.toJSONString();
		} catch (Exception e) {
			throw new EventException(e);
		}
	}

	private long getId(long timemillis) {
		Random random = new Random();
		return timemillis * 10 + random.nextInt(10);
	}

	private long version() {
		Calendar calendar = Calendar.getInstance();
		int year = calendar.get(Calendar.YEAR);
		int month = calendar.get(Calendar.MONTH) + 1;
		int day = calendar.get(Calendar.DATE);
		return year * 10000 + month * 100 + day;
	}

	@Override
	public <T extends Event> List<T> findEvents(Class<T> eventType, long version) {
		HashMap<String, Object> params = new HashMap<String, Object>();
		params.put("type", eventType.getName());
		params.put("version", version);

		List<EventLog> eventLogs = this.query.selectList(
				"EventStore.findEventsOfVersion", params);
		return toEventList(eventLogs);
	}

	@Override
	public <T extends Event> List<T> findEventRange(Class<T> eventType,
			long beginTimemillis, long endTimemillis) {
		HashMap<String, Object> params = new HashMap<String, Object>();
		params.put("type", eventType.getName());
		params.put("begin", beginTimemillis);
		params.put("end", endTimemillis);
		List<EventLog> eventLogs = this.query.selectList(
				"EventStore.findEventsOfTypeCreatedRange", params);
		return toEventList(eventLogs);
	}

	@Override
	public List findEventRange(long beginTimemillis, long endTimemillis) {
		HashMap<String, Object> params = new HashMap<String, Object>();
		params.put("begin", beginTimemillis);
		params.put("end", endTimemillis);
		List<EventLog> eventLogs = this.query.selectList(
				"EventStore.findEventsOfCreatedRange", params);
		return toEventList(eventLogs);
	}

	private List toEventList(List<EventLog> eventLogs) {
		ArrayList<Event> results = new ArrayList<Event>();
		for (EventLog log : eventLogs) {
			Event event = toEventObject(log.getType(), log.getData());
			results.add(event);
		}
		return results;
	}
}
